/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/

//实现promise all函数

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function resolvePromise(promise2,x, resolve,reject){
    if(promise2 === x){
        return reject(new TypeError("Chaining cycle detected for promise #<Promise>"))
    }
    if(x instanceof MyPromise){
        // promise对象
        x.then(resolve,reject)
    }else{
        // 普通值
        resolve(x)
    }
}
class MyPromise {
    constructor(executor){  // executor 执行器
        try {
            // 立即执行
            executor(this.resolve,this.reject)
        } catch (error) {
            this.reject(error.message)
        }
        
    }
    // promise 状态
    status = PENDING
    // 成功之后的值
    value= undefined
    // 失败之后的原因
    err= undefined

    // 成功回调
    successCallback= []
    // 失败回调
    failCallback= []

    resolve= value=>{
        // 如果状态不是等待 阻止程序继续执行
        // 将状态改为成功
        if(this.status !== PENDING) return
        this.status= FULFILLED
        // 保存成功的值，then函数要拿到
        this.value= value
        // 判断成功回调是否存在 如果存在  调用
        // this.successCallback && this.successCallback(this.value)
        while(this.successCallback.length) this.successCallback.shift()()
    }
    reject= reason=>{
        if(this.status !== PENDING) return
        this.status= REJECTED
        // 保存失败原因
        this.err= reason
        // 判断成功回调是否存在 如果存在  调用
        // this.failCallback && this.failCallback(this.value)
        while(this.failCallback.length) this.failCallback.shift()()
    }

    then(successCallback,failCallback){ //接收两个回调函数 成功 失败
        // 如果then没有接收到成功和失败回调函数，直接返回结果或者抛出错误
        successCallback= successCallback? successCallback : value => value
        failCallback= failCallback? failCallback : err=>{
            throw err
        }
        // 定义一个新的promise对象实现链式调用
        let newPromise= new MyPromise((resolve,reject)=>{
            if(this.status === FULFILLED){
                setTimeout(()=>{
                    try {
                        // 拿到上一次then函数返回的值
                        let x= successCallback(this.value)
                        // resolve(x)
                        //传入的参数一和参数二为了对比返回的是否是当前自身这个promise对象
                        resolvePromise(newPromise,x,resolve,reject)
                    } catch (error) {
                        reject(error.message)
                    }
                },0)
            }else if(this.status === REJECTED){
                setTimeout(()=>{
                    try {
                        // 拿到上一次then函数返回的值
                        let x= failCallback(this.err)
                        // resolve(x)
                        //传入的参数一和参数二为了对比返回的是否是当前自身这个promise对象
                        resolvePromise(newPromise,x,resolve,reject)
                    } catch (error) {
                        reject(error.message)
                    }
                },0)
            }else{
                // 执行器是异步操作时，status还是pending，
                // 所以此时并不知道是成功还是失败回调，那么这里就需要把这两个回调
                // 存储起来
                this.successCallback.push(()=>{
                    setTimeout(()=>{
                        try {
                            // 拿到上一次then函数返回的值
                            let x= successCallback(this.value)
                            // resolve(x)
                            //传入的参数一和参数二为了对比返回的是否是当前自身这个promise对象
                            resolvePromise(newPromise,x,resolve,reject)
                        } catch (error) {
                            reject(error.message)
                        }
                    },0)
                }),
                this.failCallback.push(()=>{
                    setTimeout(()=>{
                        try {
                            // 拿到上一次then函数返回的值
                            let x= failCallback(this.err)
                            // resolve(x)
                            //传入的参数一和参数二为了对比返回的是否是当前自身这个promise对象
                            resolvePromise(newPromise,x,resolve,reject)
                        } catch (error) {
                            reject(error.message)
                        }
                    },0)
                })
            }
        })
        return newPromise

    }

    static all(array){
        // 因为all函数的执行会返回一组数组，在then的resolve中拿到
        let result= []
        // array中可能存在promise的异步代码，而for循环又是同步任务
        // 所以每次addData的时候可以通过index自增后的值与array的
        // 长度做对比，因为当异步任务的时候addData函数肯定还没执行嘛，
        // index就不可能跟array.length相等，那等异步任务执行结束之后，
        // addData也调用了，index也自增了，两者相等了就可以执行resolve
        let index= 0
        
        return new MyPromise((resolve,reject)=> {
            // 解释一下addData的作用：
            // function p1(){
            //     return new Promise((resolve,reject)=>{
            //         setTimeout(function(){
            //             resolve("c")
            //         },2000)
            //     })
            // }
            // function p2(){
            //     return new Promise((resolve,reject)=>{
            //         resolve("d")
            //     })
            // }
            // Promise.all(["a","b",p1(),p2(),"e"]).then(res=>console.log(res))
            // 在原生的promise的all方法中，即便p1是个异步任务，他也会按照我们传入
            // 的顺序进行结果的打印输出 ['a','b','c','d','e']所以我们才需要一个新的数组result
            // 来存放array中每一项的结果值，以及for循环中的索引值，来在result中占个位，
            // 比如p1结束异步任务后，调用addData传入p1所在数组的索引值和执行结果。
            function addData(key,value){
                result[key]= value;
                index++
                if(index === array.length){
                    resolve(result)
                }
            }
            for(let i=0;i<array.length; i++){
                /**
                 * 遍历all传入的数组，如果是普通值直接存入result数组，
                 * 如果是promise对象，就执行完then函数拿到结果再放入数组，
                 * 
                 */
                let current= array[i]
                if(current instanceof MyPromise){
                    //promise
                    current.then(value=> addData(i,value), reason=> reject(reason))
                }else{
                    // 普通值
                    addData(i,array[i])
                }
            }
        })
    }

    static resolve(value){
        if(value instanceof MyPromise) return value
        return new MyPromise( resolve=> resolve(value) )
    }
    finally(callback){
        return this.then(value=>{
            return MyPromise.resolve(callback()).then(()=>value)
        },reason=>{
            return MyPromise.resolve(callback()).then(()=>{
                throw reason
            })
        })
    }
}

module.exports= MyPromise